#pragma once

#include <set>
#include <stdexcept>
#include <ostream>

#include "Horse.h"
#include "Direction.h"


class Board
{
public:
	Board(const size_t size):
		size(size),
		horsesCount(0),
		q(0)
	{
		horses = std::set<Horse>();
	}

	//vymaze kone a do horniho trojuhelniku umisti dany pocet koni.
	void putHorses(const size_t _count)
	{
		size_t cnt = _count;
		if (cnt < size || cnt > size * (size + 1) / 2)
		{
			throw std::out_of_range("Horses count must be between 1 and n*(n+1)/2!");
		}
		horses.clear();
		horsesCount = cnt;
		size_t y = 0, x = 0; //zacnu nahore vlevo
		size_t zarazka = size;
		while (cnt > 0)
		{
			horses.insert(Horse(x,y));
			x++;
			if (x>=zarazka)
			{
				x = 0; //zpet na zacatek radku
				y++; //po radcich dolu
				zarazka--;
			}
			cnt--;
		}
	}

	//Test, zda dane policko sachovnice je prazdne
	bool isEmpty(const size_t x, const size_t y) const
	{
		Horse tmp = Horse(x,y);
		return horses.find(tmp) == horses.end(); //pokud vrati iterator na end, tak to tam neni.
	}

	//Zamicha kone na sachovnici.
	void shuffle(const size_t _q)
	{
		if (horsesCount == 0)
		{
			std::cerr << "No horses placed yet." << std::endl;
			return;
		}
		if (_q < size || _q > size*size)
		{
			std::cerr << "_q must be between n and n*n" << std::endl;
			throw std::out_of_range("_q must be between n and n*n");
		}
		this->q = _q;
		size_t times = _q;
		while (times != 0)
		{
			size_t rx = rand() % this->size;
			size_t ry = rand() % this->size;
			if (move(Horse(rx,ry),Direction::random()))
			{
				times--;	
			}
		}
	}

	//Pohne konem na zadane pozici v zadanem smeru a vrati true.
	//Pokud je dane pole prazdne, nebo by vysledna poziva byla mimo desku,
	//nic se nestane a vrati false.
	bool move(const Horse& h, const Direction& d)
	{
		std::set<Horse>::iterator i = horses.find(h);
		if(i!=horses.end()) //zadane pole je prazdne, neni tam kun
		{
			Horse hnew = Horse(h.getX()+d.dx,h.getY()+d.dy);
			if(checkBounds(hnew) && horses.find(hnew)==horses.end())
			{
				horses.erase(i); //smaz puvodni pozici
				horses.insert(hnew); //umisti novou
				return true;
			}
		}
		return false;
	}

	//Checks whether the horse is actually on the board, within the bounding indexes.
	bool checkBounds(const Horse& h) const
	{
		size_t x = h.getX();
		size_t y = h.getY();
		if ( x<0 || y<0 || x>=this->size || y>=this->size )
		{
			return false;
		}
		return true;
	}

	//zkraceny vypis do streamu
	friend std::ostream& operator<< (std::ostream& os, const Board& brd)
	{
		for(size_t y = 0; y < brd.size; y++)
		{
			for (size_t x = 0; x < brd.size; x++)
			{
				if (brd.isEmpty(x,(size_t)y))
				{
					os << " .";
				}
				else
				{
					os << " X";
				}
			}
			os << '\n';
		}
		os  << "Horses count: " << brd.horses.size() << '\n';
		return os;
	}
	
	//Vypis do souborove reprezentace, probiha do zadaneho streamu
	//(napr. std::cout, nebo nejaky filestream).
	void printFileFormat(std::ostream &os)
	{
		os << "# board size:\n";
		os << this->size << '\n';

		os << "# n*n penalties:\n";
		for(size_t i = 0; i < this->size; i++)
		{
			for(size_t j = 0; j < this->size; j++)
			{
				os << rand() % 100 << ' ';
			}
			os << '\n';
		}

		os << "# horses count:\n" << this->horsesCount << '\n';
		os << "# horses coordinates:\n";
		for(std::set<Horse>::iterator i = horses.begin(); i != horses.end(); i++)
		{
			os << i->getX() << ' ' << i->getY() << '\n';
		}
		os << "# random moves count (q):\n";
		os << this->q << '\n';
	}

	virtual ~Board()
	{
	}

	private:
	const size_t size; //velikost desky
	size_t horsesCount, q; // pocet konu a pocet zamichani
	std::set<Horse> horses; //mnozina obsazenych poli
};
